#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <ctype.h>
#include <errno.h>
#include "err.h"
#include "amf.h"
#include "libamf.h"
/*
-var.var=value

{ NUMBER | STRING | true | false | null | undefined }
*/

static struct editinfo *parse_keyword(const char *s)
{
	struct editinfo d[2], *a;

	if (strcmp(s, "true") == 0) {
		d->srclen = 2;
		d->src[0] = AMF_BOOLEAN;
		d->src[1] = 1;
	} else if (strcmp(s, "false") == 0) {
		d->srclen = 2;
		d->src[0] = AMF_BOOLEAN;
		d->src[1] = 0;
	} else if (strcmp(s, "null") == 0) {
		d->srclen = 1;
		d->src[0] = AMF_NULL;
	} else if (strcmp(s, "undefined") == 0) {
		d->srclen = 1;
		d->src[0] = AMF_UNDEFINED;
	} else if (strcmp(s, "unsupported") == 0) {
		d->srclen = 1;
		d->src[0] = AMF_UNSUPPORTED;
	} else {
		return 0;
	}

	a = (struct editinfo *)malloc(sizeof(*a) + d->srclen);
	if (!a)
		return 0;
	memcpy(a, d, sizeof(*d) + d->srclen);
	return a;
}

static inline unsigned char hex(unsigned char c)
{
	if (c <= '9')
		return c - '0';
	if (c >= 'a')
		return c - 'a' + 10;
	return c - 'A' + 10;
}

static const char *unescape(const char *s, char *ret)
{
	static const char esc[256] = {
		['"'] = '"', ['\\'] = '\\', ['a'] = '\a',
		['b'] = '\b', ['f'] = '\f', ['n'] = '\n',
		['r'] = '\r', ['t'] = '\t', ['v'] = '\v',
	};
	unsigned char byte, num;

	byte = *s;
	if (esc[byte]) {
		num = esc[byte];
	} else if (byte == 'x') {
		/* \xhh */
		byte = s[1];
		if (!isxdigit(byte))
			goto err;
		num = hex(byte);
		s++;
		byte = s[1];
		if (!isxdigit(byte))
			goto out;
		num = 16 * num + hex(byte);
		s++;
	} else if (byte >= '0' && byte <= '7') {
		/* \ooo */
		num = byte - '0';
		byte = s[1];
		if (byte < '0' || byte > '7')
			goto out;
		num = 8 * num + byte - '0';
		s++;
		byte = s[1];
		if (byte < '0' || byte > '7')
			goto out;
		num = 8 * num + byte - '0';
		s++;
	} else {
		goto err;
	}
out:
	if (ret)
		*ret = (char)num;
	return s;
err:
	return 0;
}

static struct editinfo *parse_string(const char *str)
{
	size_t len, hdr;
	bool escaped;
	const char *s;
	struct editinfo *a;

	escaped = false;
	len = 0;
	for (s = str; *s; s++) {
		if (escaped) {
			s = unescape(s, 0);
			if (!s)
				break;
			escaped = false;
		} else if (*s == '\\') {
			escaped = true;
			continue;
		} else if (*s == '"') {
			break;
		}
		len++;
	}
	if (escaped) {
		/* bad escape */
		return 0;
	}
	if (*s != '"') {
		/* string not ended */
		return 0;
	}
	if (s[1] != 0) {
		/* trailing garbage */
		return 0;
	}

	if (len < 65536)
		hdr = 2;
	else
		hdr = 4;
	a = (struct editinfo *)malloc(sizeof(*a) + hdr + len);
	if (!a)
		return 0;
	a->srclen = hdr + len;
	if (len < 65536)
		writeu16(a->src, len);
	else
		writeu32(a->src, len);

	escaped = false;
	len = 0;
	for (s = str; *s; s++) {
		if (escaped) {
			s = unescape(s, &a->src[hdr + len]);
			escaped = false;
		} else if (*s == '\\') {
			escaped = true;
			continue;
		} else if (*s == '"') {
			break;
		} else {
			a->src[hdr + len] = *s;
		}
		len++;
	}

	return a;
}

static struct editinfo *parse_number(const char *str)
{
	double d;
	char *end;
	struct editinfo *a;

	errno = 0;
	d = strtod(str, &end);
	if (errno) {
		perrmsg("amf");
		return 0;
	}
	if (str == end || *end != 0)
		return 0;

	a = (struct editinfo *)malloc(sizeof(*a) + 9);
	if (!a)
		return 0;
	a->srclen = 9;
	a->src[0] = AMF_NUMBER;
	writef64(a->src + 1, d);
	return a;
}

static struct editinfo *parse_value(const char *str)
{
	struct editinfo *r;
	if (*str == '"')
		return parse_string(str + 1);
	r = parse_keyword(str);
	if (r)
		return r;
	return parse_number(str);
}






struct path_node {
	struct path_node *prev, *next;
	const char *pos;
	size_t len;
	bool is_number;
};

static struct path_node *new_node(bool is_number, const char *pos, size_t len)
{
	struct path_node *n;
	n = (struct path_node *)malloc(sizeof(*n));
	if (!n)
		return 0;
	n->is_number = is_number;
	n->pos = pos;
	n->len = len;
	return n;
}

static struct path_node *push(struct path_node *root, struct path_node *n)
{
	n->next = root;
	n->prev = 0;
	if (!root)
		return n;
	root->prev = n;
	return n;
}
static struct path_node *pop(struct path_node *root)
{
	struct path_node *newroot;
	if (!root)
		return 0;
	newroot = root->next;
	if (newroot)
		newroot->prev = 0;
	free(root);
	return newroot;
}

static void clear(struct path_node *root)
{
	while (root)
		root = pop(root);
}





struct context {
	struct amf_ioctx *in;
	struct path_node *path;
	struct path_node *spec;
	size_t result_off;
	size_t result_len;
	jmp_buf jmpenv;
	bool has_result;
};

static void _scan_utf8(struct context *c, int hdr)
{
	uint32_t length;

	if (hdr == 2)
		length = getu16(c->in);
	else
		length = getu32(c->in);

	seek(c->in, length);
}

static inline void scan_utf8_long(struct context *c)
{
	_scan_utf8(c, 4);
}

static inline void scan_utf8(struct context *c)
{
	_scan_utf8(c, 2);
}

static void scan_value(struct context *c);
static bool is_specified(struct context *c);
static void scan_object(struct context *c, char type)
{
	size_t start;

	if (type == AMF_TYPED_OBJECT)
		scan_utf8(c);
	else if (type == AMF_ECMA_ARRAY)
		seek(c->in, 4);

	while (peeku24(c->in) != AMF_OBJECT_END) {
		c->path = push(c->path, new_node(false, ptr(c->in) + 2,
						peeku16(c->in)));
		scan_utf8(c);
		start = c->in->pos;
		scan_value(c);
		if (is_specified(c)) {
			c->result_off = start;
			c->result_len = c->in->pos - start;
		}
		c->path = pop(c->path);
	}
}

static void scan_strict_array(struct context *c)
{
	uint32_t length, i;
	size_t start;

	length = getu32(c->in);

	for (i = 0; i < length; i++) {
		c->path = push(c->path, new_node(true, 0, i));
		start = c->in->pos;
		scan_value(c);
		if (is_specified(c)) {
			c->result_off = start;
			c->result_len = c->in->pos - start;
		}
		c->path = pop(c->path);
	}
}

static void scan_value(struct context *c)
{
	char type;

	type = getu8(c->in);
	switch (type) {
	case AMF_NULL:
	case AMF_UNDEFINED:
	case AMF_UNSUPPORTED:
		break;
	case AMF_BOOLEAN:
		seek(c->in, 1);
		break;
	case AMF_REFERENCE:
		seek(c->in, 2);
		break;
	case AMF_NUMBER:
		seek(c->in, 8);
		break;
	case AMF_DATE:
		seek(c->in, 10);
		break;
	case AMF_STRING:
		scan_utf8(c);
		break;
	case AMF_LONG_STRING:
	case AMF_XML_DOCUMENT:
		scan_utf8_long(c);
		break;
	case AMF_OBJECT:
	case AMF_ECMA_ARRAY:
	case AMF_TYPED_OBJECT:
		scan_object(c, type);
		break;
	case AMF_STRICT_ARRAY:
		scan_strict_array(c);
		break;
	default:
		pmsgmsg("amf", "unknown marker");
		longjmp(c->in->jmpenv, 2);
	}
}




static bool is_specified(struct context *c)
{
	struct path_node *p, *s;
	char buf[sizeof(size_t) * 16 + 3];

	if (c->has_result)
		return false;
	/*
	path	spec	match
	a.b.c	b.c	yes
	b.c	a.b.c	no
	*/
	for (p = c->path, s = c->spec; p && s; p = p->next, s = s->next) {
		if (p->is_number) {
			p->len = sprintf(buf, "%u", p->len);
			p->pos = buf;
		}
		if (p->len != s->len ||	memcmp(p->pos, s->pos, p->len))
			return false;
	}
	if (!p && s)
		return false;
	return true;
}

static struct path_node *split_dot(const char *s)
{
	const char *dot;
	struct path_node *root;

	root = 0;
	while ((dot = strchr(s, '.')) != 0) {
		root = push(root, new_node(false, s, dot - s));
		s = dot + 1;
	}
	if (s[0])
		root = push(root, new_node(false, s, strlen(s)));
	return root;
}

struct editinfo *amf_edit(const char *base, unsigned size,
				const char *specifier, const char *value)
{
	struct context *c;
	struct editinfo *r;

	c = (struct context *)malloc(sizeof(*c));
	if (!c)
		die(1);
	c->has_result = false;
	c->in = (struct amf_ioctx *)malloc(sizeof(*c->in));
	if (!c->in)
		die(1);
	c->in->base = base;
	c->in->pos = 0;
	c->in->end = size;
	c->path = 0;
	c->spec = split_dot(specifier);
	if (setjmp(c->in->jmpenv) == 0) {
		if (getu8(c->in) != AMF_STRING)
			return 0;
		c->path = push(c->path, new_node(false, ptr(c->in) + 2,
						peeku16(c->in)));
		scan_value(c);
		c->path = pop(c->path);
	}
	clear(c->path);
	clear(c->spec);
	r = 0;
	if (c->has_result) {
		r = parse_value(value);
		if (r) {
			r->dstoff = c->result_off;
			r->dstlen = c->result_len;
		}
	}
	free(c->in);
	free(c);
	return r;
}
